/*************************************************************************************
*                                  SRL ENGINE                                        *
*                                                                                    *
*                                                                                    *
*  File : SRL_FrameBuffer.cpp                                                        *
*  Author: SRL Killing                                                               *
*  Date: 25/02/10 20:30                                                              *
*  Description:                                                                      *
*                                                                                    *  
*  Licence :  LGPL                                                                   *
*                                                                                    *
*    Copyright (C) 2010 Pecatte Baptiste                                             *
*                                                                                    *
*    This program is free software; you can redistribute it and/or                   *
*    modify it under the terms of the GNU Lesser General Public                      *
*    License as published by the Free Software Foundation; either                    *
*    version 2.1 of the License, or (at your option) any later version.              *
*                                                                                    *
*    This program is distributed in the hope that it will be useful,                 *
*    but WITHOUT ANY WARRANTY; without even the implied warranty of                  *
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU                *
*    Lesser General Public License for more details.                                 *
*                                                                                    *
*    You should have received a copy of the GNU Lesser General Public                *
*    License along with this program; if not, write to the Free Software             *
*    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA    *
*                                                                                    *
**************************************************************************************/

#include <string>
#include <fstream>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SRL/Utils/Core.h>
#include <SRL/Utils/Extensions.h>
#include <SRL/Engine/FrameBuffer.h>

namespace srl {
    
/*********************************************************************************************************
***
***		FBO Class
***
*********************************************************************************************************/

    FrameBuffer::FrameBuffer(int w, int h, int nbTex) {
    	FboConfig::__Singleton();
    	
    	// Initialize Texture Array
    	mTex = new Texture*[10];
    	mTexMadeIn = new bool[10];
    	for(int i = 0; i < 10; i++) {
    		mTex[i] = NULL;
    		mTexMadeIn[i] = false;
    	}
    		
    	// Generate textures asked
    	for(int i = 0; i < nbTex; i++) {
			mTex[i] = new Texture();
			mTexMadeIn[i] = true;
		}
        
        // FrameBuffer
        glGenFramebuffersEXT(1, &fbo);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
        
        // DepthBuffer
        glGenRenderbuffersEXT(1, &dbo);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, dbo);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, dbo);
        	
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
        
    }
    
    FrameBuffer::~FrameBuffer() {
        glDeleteFramebuffersEXT(1, &fbo);
        glDeleteRenderbuffersEXT(1, &dbo);
        for(int i = 0; i < 10; i++)
        	if(mTexMadeIn[i] == true)
    			delete mTex[i];
    			
    	delete mTex;
    	delete mTexMadeIn;
    }
    
    
    
    void FrameBuffer::Init() {
    	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
    	for(int i=0; i < 10; i++) {
    		if(mTex[i] != NULL) {
				if(mTex[0]->mFormat != mTex[i]->mFormat)
					SRL_Except("Les formats des textures pour le FBO doivent êtres identiques");
					
				if(mTex[0]->mWidth != mTex[i]->mWidth || mTex[0]->mHeight != mTex[i]->mHeight)
					SRL_Except("Les textures pour le FBO doivent êtres de même taille");
				
		        mTex[i]->BindFBO( GL_COLOR_ATTACHMENT0_EXT);// + i );
            }
        }
       	//if(CheckStatus() == GL_FRAMEBUFFER_COMPLETE) printf("ITS OKEYYYY \n\n");
       	
    	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    }
    
    
    GLenum FrameBuffer::CheckStatus() {
    	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    	switch (status) {
    		case GL_FRAMEBUFFER_UNDEFINED: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_UNDEFINED"); break;
    		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); break;
    		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); break;
    		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); break;
    		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); break;
    		case GL_FRAMEBUFFER_UNSUPPORTED: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_UNSUPPORTED"); break;
    		case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); break;
    		case GL_FRAMEBUFFER_COMPLETE: break;
    		default:  printf("Framebuffer status return : 0x%x \n\n", status); break;
    		//case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: SRL_Except("Framebuffer status return : GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"); break;
    	}
    }
    
    
    
    void FrameBuffer::LinkTexture(srl::Texture* tex, int i) {
    	if(tex == NULL)
			SRL_Except("Le pointeur de texture passe au FBO est null");
			
    	// If texture was made in this class by "new" identifier, delete it before replace
    	if(mTexMadeIn[i] == true)
			delete mTex[i];
			
		if(tex->Test())
    		mTex[i] = tex;
    }
    void FrameBuffer::DeleteTexture(int i, bool replace) {
    
    	// Del and replace a made-in texture is useless
    	if( !(mTexMadeIn[i] && replace) ) {
    	
    		// If texture was made by constructor, delete it
    		if(mTexMadeIn[i])
    			delete mTex[i];
    		// Else, just detach the pointer
    		else
    			mTex[i] = 0;
    			
    		// If asked, replace by another FrameBuffer class texture
    		if(replace)
    			mTex[i] = new Texture();
    	}
    }
    
    
    
    void FrameBuffer::Use() {
        glPushAttrib(GL_VIEWPORT_BIT);
        
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }
    void FrameBuffer::DUse() {
        glPopAttrib();
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        
        /*for(int i=0; i < 10; i++) {
       		if(mTex[i] != NULL)
        	    mTex[i]->GenerateMipmap();
        }*/
            
        glBindTexture(GL_TEXTURE_2D, 0);
    }
    void FrameBuffer::Bind(int i) {
        glBindTexture(GL_TEXTURE_2D, mTex[i]->mId);
    }
    void FrameBuffer::DUseAndBind(int i) {
        DUse();
        Bind(i);
    }
    
    
    Texture *FrameBuffer::GetTex(int i) {
    	if(i > 10)
    		SRL_Except("The texture you want to get doesn't exist. FBO has only 10 Textures");
    	else if(mTex[i] == NULL)
    		SRL_Except("Try to acess to a NULL pointer (Texture id)");
        else
        	return mTex[i];
        	
        return 0;
    }
    
    
/*********************************************************************************************************
***
***		FboConfig Class
***
*********************************************************************************************************/
    
    FboConfig::FboConfig() { __tryConfigs(); }
    FboConfig::~FboConfig() {}
    
    const GLenum FboConfig::stencilFormats[] = {
		GL_NONE,
		GL_STENCIL_INDEX1_EXT,
		GL_STENCIL_INDEX4_EXT,
		GL_STENCIL_INDEX8_EXT,
		GL_STENCIL_INDEX16_EXT
	};
    const GLenum FboConfig::depthFormats[] = {
		GL_NONE,
		GL_DEPTH_COMPONENT16,
		GL_DEPTH_COMPONENT24,
		GL_DEPTH_COMPONENT32,
		GL_DEPTH24_STENCIL8_EXT
	};

    void FboConfig::__tryConfigs() {
    
    	GLuint fbo, tex;
    	GLenum target = GL_TEXTURE_2D;
    	glGenFramebuffersEXT(1, &fbo);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
        
        GLint drawbuffer, readbuffer;
        glGetIntegerv (GL_DRAW_BUFFER, &drawbuffer);
        glGetIntegerv (GL_READ_BUFFER, &readbuffer);
        
    	for(int i = 0; i < 15; i++) {
    		mConfs[i].valid = false;
    		GLenum format = getFormat(i);
    		//printf("Format n %i : ", i);
    		
    		if(format == GL_NONE && i != 0)
                continue;

			/*if(PixelUtil::isCompressed((PixelFormat)x))
				continue;*/

			/* Correct bug in ATI for non-RGB(A) format
			int depths[4];
			PixelUtil::getBitDepths((PixelFormat)x, depths);
			if(format != GL_NONE && mATIMode && (!depths[0] || !depths[1] || !depths[2]))
				continue;*/

            if(format != GL_NONE) {
            
				glGenTextures(1, &tex);
				glBindTexture(target, tex);
				
				/*if (GLEW_VERSION_1_2)
					glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 0);*/
                glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                            
				glTexImage2D(target, 0, format, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
				glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, tex, 0);
            }
            else {
				glDrawBuffer(GL_NONE);
				glReadBuffer(GL_NONE);
			}
            
            GLuint status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
            if(format == GL_NONE || status == GL_FRAMEBUFFER_COMPLETE_EXT) {
            
            	mConfs[i].valid = true;
				//printf("true \n", i);
                for (size_t depth = 0; depth < 5; ++depth) {
                
                    if (depthFormats[depth] == GL_DEPTH24_STENCIL8_EXT) {
                    
						// Packed depth/stencil format

						//#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
						// It now seems as if this workaround now *breaks* nvidia cards on Linux with the 169.12 drivers on Linux
						#if 0
												// Only query packed depth/stencil formats for 32-bit
												// non-floating point formats (ie not R32!) 
												// Linux nVidia driver segfaults if you query others
												if (PixelUtil::getNumElemBits((PixelFormat)x) != 32 ||
												    PixelUtil::isFloatingPoint((PixelFormat)x))
												{
												    continue;
												}
						#endif

                        if (__tryFormat(GL_DEPTH24_STENCIL8_EXT)) {
                        
                        	//printf("   DepthAndStencil : true\n");
                            DepthAndStencil mode;
                            mode.depth = depth;
                            mode.stencil = GL_NONE;
                            mConfs[i].DAndS.push_back(mode);
                        }
                        
                    }
                    else {
                    	for (size_t stencil = 0; stencil < 5 ; ++stencil) {

                            if (__tryFormat(depthFormats[depth], stencilFormats[stencil]))  {
                            	//printf("   Depth : %i  Stencil : %i \n", depth, stencil);
                                DepthAndStencil mode;
                                mode.depth = depth;
                                mode.stencil = stencil;
                                mConfs[i].DAndS.push_back(mode);
                            }
                        }
                        
                    }
                }

            }
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
			glFinish();
			
            if (format != GL_NONE)
                glDeleteTextures(1, &tex);
        }
        
        glDrawBuffer(drawbuffer);
        glReadBuffer(readbuffer);
        
        glDeleteFramebuffersEXT(1, &fbo);
    }
    
    bool FboConfig::__tryFormat(GLenum depthFormat, GLenum stencilFormat) {
        GLuint status, rboD, rboS;
        bool failed = false;
        
		// Depth RBO
        if(depthFormat != GL_NONE) {

            glGenRenderbuffersEXT(1, &rboD);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboD);
            
            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, depthFormat, 1024, 1024);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboD);
        }
		// Stencil RBO
        if(stencilFormat != GL_NONE) {
            glGenRenderbuffersEXT(1, &rboS);
            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboS);
            
            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, stencilFormat, 1024, 1024);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboS);
        }
        

        status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
        
        // Detach and destroy
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
        if(rboD)
            glDeleteRenderbuffersEXT(1, &rboD);
        if(rboS)
            glDeleteRenderbuffersEXT(1, &rboS);
        
        // If it hasn't failed, and framebuffer is ok
        return status == GL_FRAMEBUFFER_COMPLETE_EXT && !failed;
    }
    
    bool FboConfig::__tryFormat(GLenum packedFormat)
    {
        GLuint RBO;
        bool failed = false;

		// Depth AND Stencil RBO
        if(packedFormat != GL_NONE) {
        	glGenRenderbuffersEXT(1, &RBO);
        	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, RBO);

		    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, packedFormat, 1024, 1024);
		    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, RBO);
		    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, RBO);
		}

        GLuint status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

        // Detach and destroy
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
        if(RBO)
        	glDeleteRenderbuffersEXT(1, &RBO);
        	
		// If it hasn't failed, and framebuffer is ok
        return status == GL_FRAMEBUFFER_COMPLETE_EXT && !failed;
    }
    
    GLenum FboConfig::getFormat(int i) {
    	switch (i) {
    		
    		// None
    		default: return GL_NONE; break;
    		case 0: return GL_NONE; break;
    		
    		// Greys
            case 1: return GL_LUMINANCE8; break;
            case 2: return GL_LUMINANCE16; break;
            case 3: return GL_ALPHA8; break;
            case 4: return GL_LUMINANCE4_ALPHA4; break;
			case 5: return GL_LUMINANCE8_ALPHA8; break;
			case 6: return GL_LUMINANCE16_ALPHA16; break;
			
			// RGB(A)
			case 7: return GL_RGB5; break;
            case 8: return GL_RGBA4; break;
			case 9: return GL_RGB8; break;
            case 10: return GL_RGBA8; break;
            case 11: return GL_RGB16; break;
            case 12: return GL_RGBA16; break;
            
            case 13: return GL_RGB5_A1; break;
            case 14: return GL_RGB10_A2; break;
			case 15: return GL_R3_G3_B2; break;
			
			/*case PF_FLOAT16_R:
				return GL_LUMINANCE16F_ARB; break;
            case PF_FLOAT16_RGB:
                return GL_RGB16F_ARB; break;
			case PF_FLOAT16_GR: 
				return GL_LUMINANCE_ALPHA16F_ARB; break;
            case PF_FLOAT16_RGBA:
                return GL_RGBA16F_ARB; break;
			case PF_FLOAT32_R:
				return GL_LUMINANCE32F_ARB; break;
			case PF_FLOAT32_GR:
				return GL_LUMINANCE_ALPHA32F_ARB; break;
            case PF_FLOAT32_RGB:
                return GL_RGB32F_ARB; break;
            case PF_FLOAT32_RGBA:
                return GL_RGBA32F_ARB; break;*/
			
            
			/*case PF_SHORT_GR:
			case PF_DXT1:
				if (hwGamma)
					return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
				else
					return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break;
            case PF_DXT3:
				if (hwGamma)
					return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
				else
	                return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break;
            case PF_DXT5:
				if (hwGamma)
					return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
				else
	                return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break;*/
    	}
    }
    
};
